#coding=utf-8
'''
Created on 2017-04-20

@author: sandy
'''

import threading
import logging
import time
from twisted.internet import reactor
import redis

class RedisPoolError(Exception):
    "redis pool error"

class RedisCommand(object):
    
    def __init__(self):
        self.result = None
    
    def selectCon(self, linkcount):
        return 0
    
    def setResult(self, result):
        self.result = result
    
    def execute(self, con):
        con.ping()
    
    def finish(self, error):
        pass
    
class KeepAliveCmd(RedisCommand):

    def selectCon(self, linkcount):
        return 0
    
    def execute(self, con):
        pass

    def finish(self, error):
        pass
    
class ExecuteCmd(RedisCommand):
    def __init__(self, conindex, ctx, cmd, params, func, options):
        RedisCommand.__init__(self)
        self.conindex = conindex
        self.ctx = ctx
        self.cmd = cmd
        self.params = params
        self.func = func
        self.options = options
    
    def selectCon(self, linkcount):
        return self.conindex
    
    def execute(self, con):
        if callable(self.cmd):
            return self.cmd(con)
        return con.execute_command(self.cmd, *self.params, **self.options)
        
    def finish(self, error):
        try:
            if error:
                self.func(self.ctx, error, None)
            else:
                self.func(self.ctx, None, self.result)
        except Exception:
            logging.exception(u"ExecuteCmd.finish()")   
        finally:                 
            self.ctx = None
            self.cmd = None
            self.params = None
            self.func = None 

class RedisConnection(object):
    
    def __init__(self, index, queuesize):
        self.index = index
        self.queuesize = queuesize
        self.db = 0
        self.passwd = u''
        self.host = u''
        self.port = 6379
        self.redis = None
        self.toExit = False
        self.thread = None
        self.event = None
        self.cmdlist = []
        self.lock = None
        
    def start(self, db, passwd, host, port):
        self.db = db
        self.passwd = passwd
        self.host = host
        self.port = port
        
        self.event = threading.Event()
        self.lock = threading.Lock()
        self.thread = threading.Thread(target=self.svc)
        self.thread.start()
    
    def stop(self):
        self.toExit = True
        self.thread.join()
        self.thread = None
        self.event = None
        self.lock = None

    def putCmd(self, cmd):
        self.lock.acquire()
        if len(self.cmdlist)>self.queuesize:
            self.lock.release()
            reactor.callFromThread(cmd.finish, RedisPoolError(u"redis queue full"))
            return
        self.cmdlist.append(cmd)
        self.lock.release()
        self.event.set()
        
    def svc(self):
        lastexecutetime = time.time()
        while not self.toExit:
            if not self.redis:
                self.connect()
                if not self.redis:
                    i = 0
                    while i<50 and not self.toExit:
                        time.sleep(0.1)
                        i = i + 1
                    continue
                            
            if not self.event.wait(timeout=1):
                if time.time()-lastexecutetime>1*60:
                    self.putCmd(KeepAliveCmd())
                continue
            
            cmd = None
            self.lock.acquire()
            if len(self.cmdlist)==0:
                self.lock.release()
                self.event.clear()
                continue
            cmd = self.cmdlist.pop(0)
            self.lock.release()
            
            lastexecutetime = time.time()
            try:
                cmd.setResult(cmd.execute(self.redis))
            except Exception, e:
                reactor.callFromThread(cmd.finish, e)
                logging.exception(u"cmd.execute()")
                self.checkConnect();
                continue
            
            reactor.callFromThread(cmd.finish, None)
        if self.redis:
            del self.redis
            self.redis = None
        logging.warn(u"RedisConnection thread exit!!!")    
    
    def checkConnect(self):
        pass
        
    def connect(self):
        try:
            self.redis = redis.StrictRedis(host=self.host,port=self.port,db=self.db,password=self.passwd,socket_timeout=60)
        except Exception:
            self.redis = None
            logging.exception(u"redis %s:%d:%d connect failed", self.host, self.port, self.db)
        if self.redis:
            logging.info(u"redis %s:%d:%d connected", self.host, self.port, self.db)


class RedisConnectionPool(object):
    
    def __init__(self, db, passwd, host=u'127.0.0.1', port=6379, charset=u'utf8', linkcount=3, queuesize=512):
        self.db = db
        self.passwd = passwd
        self.host = host
        self.port = port
        self.charset = charset
        self.linkcount = linkcount
        self.queuesize = queuesize
        
        self.cons = []
        for i in range(linkcount):#线程池
            self.cons.append(RedisConnection(i, self.queuesize))
    
    def start(self):
        for con in self.cons:
            con.start(self.db,self.passwd,self.host,self.port)
    def stop(self):
        for con in self.cons:
            con.stop()
        self.cons = {}
    
    def getLinkCount(self):
        return self.linkcount
    
    def getCharset(self):
        return self.charset
            
    def putCmd(self, cmd):
        self.cons[cmd.selectCon(self.linkcount)].putCmd(cmd)

    def execute(self, conindex, ctx, cmd, params, func, options={}):
        self.putCmd(ExecuteCmd(conindex, ctx, cmd, params, func, options))

if __name__ == '__main__':
    pass